﻿using System;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;

namespace UFrameWork
{
    /// <summary>
    /// 网络客户端
    /// </summary>
    public class UNetClient
    {
        /// <summary>
        /// 主机地址
        /// </summary>
        public string Host;

        /// <summary>
        /// 主机端口
        /// </summary>
        public int Port;

        /// <summary>
        /// 消息结束标记
        /// </summary>
        public const string EndFlag = "\r$\n";

        /// <summary>
        /// 标记的数组数据
        /// </summary>
        private byte[] EngFlagBytes;

        /// <summary>
        /// 客户端Tcp对象
        /// </summary>
        private TcpClient client;

        /// <summary>
        /// 数据传输对象
        /// </summary>
        private NetworkStream netStream;

        /// <summary>
        /// 心跳包线程
        /// </summary>
        private Thread beatThread;

        /// <summary>
        /// 数据缓存池长度
        /// </summary>
        private int dataLength = 0;


        /// <summary>
        /// 数据缓存池
        /// </summary>
        private byte[] dataCache = new byte[1024 * 1024];

        /// <summary>
        /// 读取到的数据长度
        /// </summary>
        private int receiveLength = 0;

        /// <summary>
        /// 数据接收buffer
        /// </summary>
        private byte[] receiveBuffer = new byte[1024];

        /// <summary>
        /// 消息发送拼装器
        /// </summary>
        private StringBuilder sendBuilder = new StringBuilder();

        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool Connected = false;

        /// <summary>
        /// 心跳包
        /// </summary>
        public HeartBeat Beat;

        /// <summary>
        /// 连接服务器
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口</param>
        public UNetClient(string host,int port)
        {
            Host = host;
            Port = port;

            Beat = new HeartBeat(this);
            EngFlagBytes = Encoding.UTF8.GetBytes(EndFlag);
            Connect();
        }

        /// <summary>
        /// 在UI线程执行行为
        /// </summary>
        /// <param name="key"></param>
        /// <param name="param"></param>
        private void Action(string key, params object[] param)
        {
            Timer.Wait(0, () => { UWork.Invoke(key, param); });
        }

        /// <summary>
        /// 连接服务器
        /// </summary>
        private void Connect()
        {
            client = new TcpClient();
            client.BeginConnect(Host, Port, (IAsyncResult ar) =>
            {
                try
                {
                    var c = (TcpClient)ar.AsyncState;
                    if (c.Client == null) return;
                    c.EndConnect(ar);
                    if (client != c) return;
                    Connected = true;
                    Action(UEvent.NET_CONNECT_SUCCESS, this);
                    netStream = client.GetStream();
                    Receive();
                    BeatCheck(c);
                }
                catch (Exception e)
                {
                    Action(UEvent.NET_CONNECT_FAIL, this,e.Message);
                }
            }, client);
        }

        /// <summary>
        /// 接受数据
        /// </summary>
        private void Receive()
        {
            netStream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, (IAsyncResult ar) =>
            {
                try
                {
                    receiveLength = netStream.EndRead(ar);
                    Array.Copy(receiveBuffer, 0, dataCache, dataLength, receiveLength);
                    Action(UEvent.NET_RECEIVE_DATA, this, receiveBuffer, receiveLength);
                    dataLength += receiveLength;
                    UnPack();
                    if (Connected) Receive();
                }
                catch (Exception e)
                {
                    Clear(e.Message);
                }
            }, netStream);
        }

        /// <summary>
        /// 解包当前数据,并加入分发队列
        /// </summary>
        private void UnPack()
        {
            int index = GetIndexOf(dataCache, EngFlagBytes, dataLength);
            if (index ==  -1) return;
            string msg = Encoding.UTF8.GetString(dataCache, 0, index);
            int newLength = dataLength - index - EngFlagBytes.Length;
            Array.Copy(dataCache, index + EngFlagBytes.Length, dataCache, 0, newLength);
            dataLength = newLength;
            if (!Beat.IsBeatMsg(msg))
            {
                Timer.Wait(0, () => 
                {
                    UWork.Do(UEvent.NET_RECEIVE_MSG, this, msg);
                    UMsgDispatcher.Add(this, msg);
                });
            }
            if (dataLength > 0) UnPack();
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="key">名称</param>
        /// <param name="obj">数据</param>
        public void Send(string key,params object[] obj)
        {
            if (!client.Connected) return;

            sendBuilder.Remove(0, sendBuilder.Length);
            sendBuilder.Append("{key:'");
            sendBuilder.Append(key);
            sendBuilder.Append("',data:");
            sendBuilder.Append(JsonConvert.SerializeObject(obj));
            sendBuilder.Append("}");

            Action(UEvent.NET_SEND_MSG, this, sendBuilder.ToString());
            sendBuilder.Append(EndFlag);
            byte[] bytes = Encoding.UTF8.GetBytes(sendBuilder.ToString());
            Timer.Wait(0, () => { Send(bytes, 0, bytes.Length); });
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        public void Send(byte[] bytes, int index, int length)
        {
            try
            {
                netStream.Write(bytes, 0, bytes.Length);
            }
            catch (Exception e)
            {
                Clear(e.Message);
            }
        }

        /// <summary>
        /// 心跳包检测
        /// </summary>
        private void BeatCheck(TcpClient client)
        {
            beatThread = new Thread(() => 
            {
                while (true)
                {
                    try
                    {
                        if (!UWork.AppRun || !client.Connected) return;
                        if (Beat.IsTimeOut())
                        {
                            Clear("心跳包超时");
                            return;
                        }
                        Beat.SendBeat();
                    }
                    catch (Exception e)
                    {
                        Clear(e.Message);
                        return;
                    }
                    Thread.Sleep(500);
                }
            });
            beatThread.Start();
        }

        /// <summary>
        /// 清理工作
        /// </summary>
        private void Clear(string msg = "")
        {
            if (Connected)
            {
                Connected = false;
                if (UWork.AppRun) Action(UEvent.NET_CONNECT_CUT, this, msg);
            }

            if (client != null)
            {
                client.Close();
            }
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        public void Close()
        {
            Clear();
        }

        /// <summary>
        /// 重新连接服务器
        /// </summary>
        /// <param name="host">新ip</param>
        /// <param name="port">新端口</param>
        public void ReConnect(string host = "", int port = 0)
        {
            if (!string.IsNullOrEmpty(host)) Host = host;
            if (port != 0) Port = port;
            Clear();
            Connect();
        }


        /// <summary>
        /// 在byte数组中查找byte数组的位置
        /// </summary>
        /// <param name="source">搜索数据</param>
        /// <param name="find">要的的数据</param>
        /// <param name="length">数据长度</param>
        /// <returns>-1未找到</returns>
        private int GetIndexOf(byte[] source, byte[] find, int length)
        {
            if (source == null || find == null || source.Length == 0 ||
                find.Length == 0 || source.Length < find.Length)
                return -1;

            int i, j;
            for (i = 0; i < length - find.Length + 1; i++)
            {
                if (source[i] == find[0])
                {
                    for (j = 1; j < find.Length; j++)
                    {
                        if (source[i + j] != find[j])
                            break;
                    }
                    if (j == find.Length)
                        return i;
                }
            }
            return -1;
        }
    }
}